/*
 *  Copyright (C) 2004 Cidero, Inc.
 *
 *  Permission is hereby granted to any person obtaining a copy of 
 *  this software to use, copy, modify, merge, publish, and distribute
 *  the software for any non-commercial purpose, subject to the
 *  following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY IN CONNECTION WITH THE SOFTWARE.
 * 
 *  File: $RCSfile: ATronMediaRenderer.java,v $
 *
 */

package com.cidero.bridge.audiotron;

import java.net.URL;
import java.util.logging.Logger;

import org.cybergarage.upnp.device.InvalidDescriptionException;

import com.cidero.bridge.MediaRendererException;
import com.cidero.bridge.MediaRendererBridge;
import com.cidero.upnp.AVTransport;
import com.cidero.upnp.ConnectionManager;
import com.cidero.upnp.RenderingControl;
import com.cidero.util.AsyncCommand;

/**
 * This class contains the Audiotron implementation of the 
 * MediaRendererBridge interface. Audiotron commands are 
 * excuted via it's HTTP-based interface
 *
 * Only a minimal set of commands is supported at the moment:
 * 
 *   start()
 *   stop()
 *   mute()
 *   volume()
 *  
 *
 * The Audiotron API is a bit unwieldy. One would like to be able
 * to just say something like 'play http://192.168.1.100:8080/shoutcast'
 * to play back a streaming music source, but the Audiotron has to
 * have the URL to stream in it's database prior to being able
 * to use it. This rules out dynamically creating URL's and sending
 * them to the Audiotron. Instead, fixed URL's are used, and they
 * are input to the Audiotron via the 'TurtleRadio' mechanism.
 *
 * Basic logic:
 * 
 * 1. Using TurtleRadio, add a single streaming radio URL for each 
 *    Audiotron on the network, under the radio station category 
 *    you specified in the AudiotronDeviceManager.properties file.
 *
 *    The default category is 'Home'. If the devices are assigned
 *    the names 'LivingRoom', 'Bedroom' in the property file, the 
 *    URL's to use for TurtleRadio will look like:
 *
 *      http://192.168.1.100:8081/UPnPBridge/audiotron/LivingRoom
 *    
 *      http://192.168.1.100:8081/UPnPBridge/audiotron/Bedroom
 *
 * 2. The device is initialized. This consists of executing an
 *    HTTP request to get the device status.
 *
 *    The list of available radio stations installed on the Audiotron 
 *    is also checked to make sure that a streaming station with 
 *    the same name as this device exists.
 *
 * 3. Playback. The current playlist is cleared, and the playlist is set to 
 *    the home radio station category. The index of the URL for this
 *    device is selected using the 'goto' command. The playback is 
 *    started using the 'play command'
 *
 * 4. The playback is halted via a 'stop' command.
 *
 */
public class ATronMediaRenderer extends MediaRendererBridge
{
  private static Logger logger = 
    Logger.getLogger("com.cidero.bridge.audiotron");

  private final static String DESCRIPTION_FILE_NAME = 
     "com/cidero/bridge/audiotron/description/MediaRenderer.xml";

  public final static int DEFAULT_PORT = 80;
  public final static int DEFAULT_TIMEOUT = 5000;
  public final static int STATUS_REQUEST_TIMEOUT = 5000;
  public final static int DEFAULT_MONITOR_PERIOD = 2000;

  private static int instanceCount = 0; 

  int      port = DEFAULT_PORT;
  int      seqNum = 0;
  int      unitId;
  URL      url = null;   // Current URL
  String   userName;
  String   password;
  AsyncCommand asyncCommand;
  ATronStateModel atronStateModel;
  int      monitorPeriodMillisec = DEFAULT_MONITOR_PERIOD;

  // Services
  ATronRenderingControl  renderingControl;
  ATronAVTransport       avTransport;
  ATronConnectionManager connectionManager;

  /**
   * Constructor
   *
   * @param  ipAddr         IP address of Audiotron device.
   * @param  friendlyName   Friendly name of Audiotron device.
   *                        ('LivingRoom', 'Bedroom', etc...)
   */
  public ATronMediaRenderer( String ipAddr, String friendlyName,
                             String userName, String password )
    throws InvalidDescriptionException
  {
    super( DESCRIPTION_FILE_NAME, ipAddr, friendlyName );

    this.userName = userName;
    this.password = password;
    
    getProperties();

    //
    // Status model is observed by the AVTransport and RenderingControl
    // service instances
    //
    atronStateModel = new ATronStateModel();

    // Setup ATron-specific versions of UPnP services
    // Note: these constructors throw a InvalidDescriptionException if 
    // the device description doesn't have a matching service
    renderingControl = new ATronRenderingControl( this );
    avTransport = new ATronAVTransport( this );
    connectionManager = new ATronConnectionManager( this );

    unitId = instanceCount++;

    //
    // Start separate thread to handle asynchronous communication with
    // AudioTron (using HTTP). Allow for 10 queued asynchronous commands
    //
    asyncCommand = new AsyncCommand( 10 );
    AsyncCommandThread asyncCommandThread = new AsyncCommandThread( this );
    asyncCommandThread.start();

    try {
      open();
    } catch( MediaRendererException e ) {
      logger.warning( "startAsyncThread: open exception! " + e );        
    }

    // Start monitoring thread
    StatusMonitorThread monitorThread = 
      new StatusMonitorThread( this, monitorPeriodMillisec );
    monitorThread.start();
  }
  
  public RenderingControl getRenderingControl() {
    return renderingControl;
  }
  public AVTransport getAVTransport() {
    return avTransport;
  }
  public ConnectionManager getConnectionManager() {
    return connectionManager;
  }

  public ATronStateModel getStateModel()
  {
    return atronStateModel;
  }
  
  public String getUserName()
  {
    return userName;
  }
  public String getPassword()
  {
    return password;
  }
  public int getPort()
  {
    return port;
  }
  public AsyncCommand getAsyncCommand()
  {
    return asyncCommand;
  }
  

  /** 
   *  Get properties from propery file 
   */
  public void getProperties()
  {
    //Properties props = MrUtil.loadProperties("PrimiqMediaRenderer.properties");
	}

  public String send( String cmd, int timeoutMillisec )
  {
    return (String)asyncCommand.send( cmd, timeoutMillisec );
  }
  
  public void open() throws MediaRendererException
  {
    logger.info("Initializing Audiotron HTTP server at " 
                + getIPAddr() + ":" + port );

    String response;
    response = send( "/apicmd.asp?cmd=poweron", DEFAULT_TIMEOUT );
    if( response != null )
    {
      logger.fine("PowerOn response: " + response );
    }
    else
    {
      logger.warning("No response to PowerOn " );
      throw new MediaRendererException( "open: timeout");
    }
    
    response = getRendererState();
    if( response != null )
    {
      logger.fine("getRendererState response: " + response );
    }
    else
    {
      logger.warning("No response to GetStatus " );
      throw new MediaRendererException( "open: timeout");
    }
    
    logger.info("Opened Audiotron ok");
  }

  /**
   *  Get audiotron status. This allows for status info (volume, mute,
   *  pause) to be passed back to control point (if status changed 
   *  due to user using the Audiotron remote)
   */
  public synchronized String getRendererState()
  {
    String response = send( "/apigetstatus.asp", STATUS_REQUEST_TIMEOUT );
    if( response == null )
    {
      logger.warning( "No response to status request... " );        
      return null;
    }

    atronStateModel.parse( response );
    atronStateModel.notifyObservers();

    logger.fine( "STATUS Volume = " + atronStateModel.getVolume() );
    return response;
  }

  public void close() throws MediaRendererException
  {
    String response = send( "/apicmd.asp?cmd=poweroff", DEFAULT_TIMEOUT );
    if( response == null )
      throw new MediaRendererException( "close: timeout");
  }

  public String getProxyUrlPath()
  {
    return "/Audiotron/" + getFriendlyName();
  }

  public void avTransportSetTransportURI( String uri )
    throws MediaRendererException
  {
    avTransport.setTransportURI( uri );
  }
  public void avTransportPlay( String speed ) throws MediaRendererException
  {
    avTransport.play( speed );
  }
  public void avTransportPause() throws MediaRendererException
  {
    avTransport.pause();
  }
  public void avTransportStop() throws MediaRendererException
  {
    avTransport.stop();
  }


  /**
   * Simple test code
   */
  /*
  public static void main( String args[] )
  {
    try
    {
      ATronMediaRenderer renderer = 
         new ATronMediaRenderer( "192.168.1.62", "LivingRoom",
                                     "", "admin" );

      System.out.println("----------------- Clearing play queue -----------");
      renderer.clearPlayQueue();

      // Add all items in 'Home' category to queue

      // Get list of items in 'Home' category
      System.out.println("Getting Web categories --------------------");
      renderer.sendCmd( "/apigetinfo.asp?type=web" );


      System.out.println("Getting Files under Adult Contemp ----------------");
      renderer.sendCmd( "/apigetinfo.asp?type=file&fweb=Adult%20Contemporary");
      
      System.out.println("Queuing Adult contemporary --------------------");
      renderer.sendCmd( "/apiqfile.asp?type=web&file=Adult%20Contemporary" );
    
      // Position to desired item
      renderer.sendCmd( "/apicmd.asp?cmd=goto&arg=8" );

      // Position to desired item
      renderer.setVolume( "48000" );

      System.out.println("Playing ----------------------------------- ");
      renderer.play( "1.0" );

      System.out.println("Done");

    }
    catch( Exception e )
    {
      System.out.println( e );
      System.exit(-1);
    }
  }
  */

}
